/**
 * @author Aldivone Correia[aldivone@gmail.com] 27/10/2012 - 17:37:12.
 */
package br.com.ac.fatureweb.utilitary;

import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.primefaces.model.LazyDataModel;
import org.primefaces.model.SortOrder;

/**
 * 
 * @author Aldivone Correia[aldivone@gmail.com] 17/09/2013 - 17:26:26.
 * 
 * @param <Bean>
 */
@SuppressWarnings("all")
public class PageDataModel<Bean> extends LazyDataModel<Bean> implements
		Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -3788161598743298196L;

	/**
	 * 
	 */
	protected List<Object> pagingList;

	/**
	 * 
	 */
	protected Map<String, Order> ordenacao = new LinkedHashMap<String, Order>();

	/**
	 * 
	 */
	protected int count;

	/**
	 * 
	 */
	protected int pageSize;

	/**
	 * 
	 */
	protected int index;

	/**
	 * 
	 */
	public int paginaAtual;

	/**
	 * 
	 */
	protected NgcDefaultBean<Bean> service;

	/**
	 * 
	 */
	protected Map<String, Object> searchObject;

	/**
	 * 
	 */
	protected String listMethodName;

	/**
	 * 
	 */
	protected String countMethodName;

	/**
	 * 
	 * @author Aldivone Correia[aldivone@gmail.com] 17/09/2013 - 17:28:29.
	 */
	public PageDataModel() {
		super();
	}

	/**
	 * Cria uma nova inst�ncia de PagingCollectionModel.
	 * 
	 * @param pageSize
	 *            tamanho da p�gina. Indica quantos registros ser�o exibidos por
	 *            p�gina.
	 * @param facServico
	 *            fachada utilizada para buscas sob demanda
	 * @param searchObject
	 *            objeto com os par�metros da consulta.
	 */
	public PageDataModel(final int pageSize,
			final NgcDefaultBean<Bean> facServico,
			final Map<String, Object> searchObject,
			final Map<String, Order> ordenacao) {
		super();
		this.service = facServico;
		this.searchObject = searchObject;
		if (ordenacao != null) {
			this.ordenacao.putAll(ordenacao);
		}

		count = facServico.count(searchObject, this.ordenacao);

		this.pageSize = pageSize > 0 ? pageSize : count;

		// Calcula o total de p�ginas
		final int totalPages = (int) Math.ceil((double) count / pageSize);

		pagingList = new ArrayList(totalPages);

		// inicializa os dados de cada p�gina com null
		for (int i = 0; i < totalPages; i++) {
			pagingList.add(null);
		}

		// traz a primeira p�gina
		if (count > 0) {
			fetch(0);
		}
	}

	/**
	 * 
	 * Cria uma nova inst�ncia de PagingCollectionModel.
	 * 
	 * @param pageSize
	 *            tamanho da p�gina. Indica quantos registros ser�o exibidos por
	 *            p�gina.
	 * @param facServico
	 *            fachada utilizada para buscas sob demanda
	 * @param searchObject
	 *            objeto com os par�metros da consulta.
	 * @param listMethodName
	 *            Nome do m�todo listar.
	 * @param countMethodName
	 *            Nome do m�todo count.
	 * @throws InvocationTargetException
	 * @throws IllegalAccessException
	 * @throws NoSuchMethodException
	 * @throws IllegalArgumentException
	 * @since visao - 2008.
	 */
	public PageDataModel(final int pageSize,
			final NgcDefaultBean<Bean> facServico,
			final Map<String, Object> searchObject,
			final Map<String, Order> ordenacao, final String listMethodName,
			final String countMethodName) throws IllegalArgumentException,
			NoSuchMethodException, IllegalAccessException,
			InvocationTargetException {
		super();

		this.service = facServico;
		this.searchObject = searchObject;
		this.ordenacao.putAll(ordenacao);

		this.listMethodName = listMethodName;
		this.countMethodName = countMethodName;

		count = (Integer) executeMethod(this.service.getClass(), this.service,
				this.countMethodName,
				new Class<?>[] { this.searchObject.getClass() },
				new Object[] { this.searchObject });

		this.pageSize = pageSize > 0 ? pageSize : count;

		// Calcula o total de p�ginas
		final int totalPages = (int) Math.ceil((double) count / pageSize);
		pagingList = new ArrayList(totalPages);

		// inicializa os dados de cada p�gina com null
		for (int i = 0; i < totalPages; i++) {
			pagingList.add(null);
		}

		// traz a primeira p�gina
		if (count > 0) {
			fetch(0);
		}
	}

	/**
	 * Retorna o total de registros da consulta.
	 */
	@Override
	public int getRowCount() {
		return count;
	}

	/**
	 * Retorna o objeto que corresponde ao �ndice atual. Determina o �ndice da
	 * p�gina atrav�s do �ndice do item atual, depois checa se existem itens
	 * nessa p�gina. Se n�o houver, carrega os items atrav�s de um m�todo da
	 * fachada. Se houver, converte o �ndice atual para ser relativo � pagina e
	 * retorna o item.
	 * 
	 */
	@Override
	public Bean getRowData() {
		if (isRowAvailable()) {
			try {
				final int pageIndex = getPageIndex(index);
				List items = (List) pagingList.get(pageIndex);
				if (items == null || items.isEmpty()) {
					items = fetch(pageIndex);
				}

				paginaAtual = pageIndex;

				return (Bean) items.get(index - pageSize * pageIndex);
			} catch (final IndexOutOfBoundsException e) {

			}
		}
		return null;
	}

	/**
	 * Retorna os itens que correspondem a p�gina de �ndice igual a
	 * <code>pageIndex</code>.
	 * 
	 * @param pageIndex
	 *            �ndice da p�gina para carregar os itens
	 * @return
	 */
	protected List fetch(final int pageIndex) {
		final int pageSizexIndex = pageSize * pageIndex;

		final int res = count - pageSizexIndex;

		final int resxPageSize = res < pageSize ? res : pageSize;

		Collection listar = service.search(searchObject, resxPageSize,
				pageSizexIndex, ordenacao);

		final List items = new ArrayList(listar);

		pagingList.set(pageIndex, items);

		return items;
	}

	/**
	 * Retorna o �ndice da p�gina a que pertence o item de �ndice
	 * <code>itemIndex</code>.
	 * 
	 * @param itemIndex
	 *            �ndice do item. De <code>0</code> a <code>count - 1</code>.
	 * @return
	 */
	protected int getPageIndex(final int itemIndex) {
		return (int) Math.floor((double) itemIndex / pageSize);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @author Aldivone Correia[aldivone@gmail.com] 05/11/2012 - 15:33:49.
	 * 
	 * @see javax.faces.model.DataModel#getRowIndex()
	 */
	@Override
	public int getRowIndex() {
		return index;
	}

	/**
	 * Retorna <code>null</code> nesta implementa��o.
	 */
	@Override
	public Object getWrappedData() {
		return null;
	}

	/**
	 * Retorna <code>true</code> se o <code>index</code> for menor que o
	 * <code>count</code> e maior ou igual que zero.
	 */
	@Override
	public boolean isRowAvailable() {
		return getRowIndex() < count && getRowIndex() >= 0;
	}

	/**
	 * Atribui o �ndice.
	 */
	@Override
	public void setRowIndex(final int index) {
		this.index = index;
	}

	/**
	 * 
	 * @author Aldivone Correia[aldivone@gmail.com] 05/11/2012 - 15:33:24.
	 * @param key
	 * @return
	 */
	public Order getOrdenacao(final Object key) {
		return ordenacao.get(key);
	}

	/**
	 * 
	 * @author Aldivone Correia[aldivone@gmail.com] 05/11/2012 - 15:33:29.
	 * @param key
	 * @param value
	 * @return
	 */
	public Order addOrdenacao(final String key, final Order value) {
		return ordenacao.put(key, value);
	}

	/**
	 * 
	 * @author Aldivone Correia[aldivone@gmail.com] 05/11/2012 - 15:33:33.
	 * @param m
	 */
	public void addAllOrdenacao(final Map<? extends String, ? extends Order> m) {
		ordenacao.putAll(m);
	}

	/**
	 * 
	 * @author Aldivone Correia[aldivone@gmail.com] 05/11/2012 - 15:33:09.
	 * @return
	 */
	public List<?> getPagingList() {
		return pagingList;
	}

	public static Object executeMethod(final Class<?> clazz,
			final Object instance, final String methodName,
			final Class<?>[] parametersTypes, final Object[] args)
			throws NoSuchMethodException, IllegalArgumentException,
			IllegalAccessException, InvocationTargetException {

		Object result;

		if (methodName != null && !methodName.trim().isEmpty())
			throw new IllegalArgumentException(
					"O argumento ReflectionUtils.executeMethod.methodName" //$NON-NLS-1$
							+ " de ser diferente de null e de vazio."); //$NON-NLS-1$

		// pega a referencia ao metodo desejado.
		final Method method = clazz.getMethod(methodName, parametersTypes);

		// indica que a acessibilidade sera true.
		method.setAccessible(true);

		// executa o metodo e guarda a resposta dele.
		result = method.invoke(instance, args);

		return result;
	}

	@Override
	public List<Bean> load(int first, int pageSize, String sortField,
			SortOrder sortOrder, Map<String, Object> filters) {
		List<Bean> data = new ArrayList<Bean>();

		// filter
		final int pageIndex = getPageIndex(index);

		int pageAtual = 0;
		if (!(pageIndex == -1)) {
			pageAtual = pageIndex;
		}

		List<Bean> items = null;

		if (pagingList != null && !pagingList.isEmpty()) {
			items = (List<Bean>) pagingList.get(pageAtual);

			for (Bean bean : items) {
				boolean match = true;

				if (filters != null) {
					for (Iterator<String> it = filters.keySet().iterator(); it
							.hasNext();) {
						try {
							String filterProperty = it.next();
							Object filterValue = filters.get(filterProperty);
							String fieldValue = String.valueOf(bean.getClass()
									.getField(filterProperty).get(bean));

							if (filterValue == null
									|| fieldValue.startsWith(filterValue
											.toString())) {
								match = true;
							} else {
								match = false;
								break;
							}
						} catch (Exception e) {
							match = false;
						}
					}
				}

				if (match) {
					data.add(bean);
				}
			}
		}
		// rowCount
		int dataSize = data.size();
		this.setRowCount(dataSize);

		// paginate
		if (dataSize > pageSize) {
			try {
				return data.subList(first, first + pageSize);
			} catch (IndexOutOfBoundsException e) {
				return data.subList(first, first + (dataSize % pageSize));
			}
		} else {
			return data;
		}
	}

}